package com.xcode.unit.sourcecode;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.squareup.javapoet.*;
import com.xcode.unit.constant.UnitTestClassConstants;
import com.xcode.unit.model.MethodModel;
import com.xcode.unit.model.UnitTestModel;
import com.xcode.unit.util.LReflectionUtils;
import com.xcode.unit.util.LStringUtils;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;

import javax.lang.model.element.Modifier;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Type;
import java.nio.file.FileSystems;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * @author liuziying
 * @Description 构建单元测试类
 * @Date 2020/12/25
 */
public class UnitTestBuilder {

    private UnitTestModel unitTestModel = new UnitTestModel();
    private TypeSpec typeSpec;

    public static UnitTestBuilder builder() {
        return new UnitTestBuilder();
    }

    public UnitTestBuilder addSourcePackageNameModel(String sourcePackageName){
        this.unitTestModel.setSourcePackageName(sourcePackageName);
        return this;
    }

    public UnitTestBuilder addSourceClassNameModel(String sourceClassname){
        this.unitTestModel.setSourceClassName(sourceClassname);
        return this;
    }

    /**
     * 前提有 sourceClassname
     * @return
     */
    public UnitTestBuilder addClassNameModel(){
        this.unitTestModel.setClassName(unitTestModel.getSourceClassName()  + UnitTestClassConstants.NAME_SUFFIX_TEST);
        return this;
    }

    /**
     * 前提添加 sourcePackageName 和 sourceClassname
     * @return
     */
    public UnitTestBuilder addSourceClazzModel() {
        Class<?> clazz = null;
        try {
            clazz = Class.forName(unitTestModel.getSourcePackageName() + "." + unitTestModel.getSourceClassName());
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        this.unitTestModel.setSourceClazz(clazz);
        return this;
    }

    /**
     * 构建方法 前提有 sourceClazz
     */
    public UnitTestBuilder addMethodModel(){
        Map<String, CodeBlock> methodAndCodeBlock = Maps.newHashMap();
        CodeBlock codeBlockSample = CodeBlock.builder()
                .addStatement("$L", "//TODO write unit test biz")
                .addStatement("$L","//TODO write unit test biz")
                .build();
        Map<String, MethodModel> methods = LReflectionUtils.getMethods(unitTestModel.getSourceClazz());
        if(!CollectionUtils.isEmpty(methods)){
            Set<Map.Entry<String, MethodModel>> methodEntries = methods.entrySet();
            for (Map.Entry<String, MethodModel> methodEntry : methodEntries){
                methodAndCodeBlock.put(methodEntry.getKey(),codeBlockSample);
            }
        }
        this.unitTestModel.setMethodAndCodeBlock(methodAndCodeBlock);
        return this;
    }

    /**
     * 构建类框架
     * @throws ClassNotFoundException
     */
    public UnitTestBuilder addClassInfoModel() throws ClassNotFoundException {
        List<FieldSpec> generalFields = Lists.newArrayList();
        FieldSpec sourceClassNameFiled = FieldSpec.builder(unitTestModel.getSourceClazz(), LStringUtils.toLowerCaseFirst(unitTestModel.getClassName()))
                .addAnnotation(AnnotationSpec.builder(InjectMocks.class).build()).build();
        generalFields.add(sourceClassNameFiled);

        FieldSpec thrownFiled = FieldSpec.builder(ExpectedException.class, UnitTestClassConstants.THROWN)
                .initializer("$L","ExpectedException.none()")
                .addAnnotation(AnnotationSpec.builder(Rule.class).build())
                .addModifiers(Modifier.PUBLIC)
                .build();
        generalFields.add(thrownFiled);

        //获取类字段
        Map<String, Type> classField = LReflectionUtils.getClassFields(unitTestModel.getSourceClazz());
        if(!CollectionUtils.isEmpty(classField)){
            Set<Map.Entry<String, Type>> fieldEntries = classField.entrySet();
            for(Map.Entry<String, Type> fieldEntry : fieldEntries){
                generalFields.add(FieldSpec.builder(fieldEntry.getValue(), LStringUtils.toLowerCaseFirst(fieldEntry.getKey()))
                        .addAnnotation(AnnotationSpec.builder(Mock.class).build()).build());
            }
        }
        unitTestModel.setGeneralFields(generalFields);
        return this;
    }

    /**
     * 构建单元测试类
     */
    public UnitTestBuilder buildUnitTestClass(){
        //构建类信息
        TypeSpec.Builder classBuilder = TypeSpec.classBuilder(unitTestModel.getClassName());
        classBuilder.addModifiers(Modifier.PUBLIC);
        classBuilder.addAnnotation(AnnotationSpec.builder(RunWith.class)
                .addMember("value","$T.class", MockitoJUnitRunner.class).build());

        //构建类字段信息
        if(!CollectionUtils.isEmpty(unitTestModel.getGeneralFields())){
            classBuilder.addFields(unitTestModel.getGeneralFields());
        }
        //构建类方法信息
        List<MethodSpec> methodSpecs = Lists.newArrayList();
        Map<String, CodeBlock> methodAndCodeBlocks = unitTestModel.getMethodAndCodeBlock();
        if(!CollectionUtils.isEmpty(methodAndCodeBlocks)){
            Set<Map.Entry<String, CodeBlock>> entries = methodAndCodeBlocks.entrySet();
            for (Map.Entry<String, CodeBlock> entry : entries){
                methodSpecs.add(MethodSpec.methodBuilder(entry.getKey()).returns(void.class)
                        .addAnnotation(AnnotationSpec.builder(Test.class).build())
                        .addCode(entry.getValue().toBuilder().build())
                        .addModifiers(Modifier.PUBLIC)
                        .build());
            }
        }

        if(!CollectionUtils.isEmpty(methodSpecs)){
            classBuilder.addMethods(methodSpecs);
        }
        typeSpec = classBuilder.build();
        return this;
    }

    /**
     * 创建单元测试类
     */
    public void create(){
        try {
            //Java文件生成
            JavaFile javaFile= JavaFile.builder(unitTestModel.getSourcePackageName(), typeSpec).build();
            if(ObjectUtils.isEmpty(unitTestModel.getToDirectory())){
                javaFile.writeTo(FileSystems.getDefault().getPath("src/test/java/"));
            }else {
                javaFile.writeTo(new File(unitTestModel.getToDirectory()));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
